#include "osgQtWidget.h"
#include <iostream>
#include <osg/DeleteHandler>
#include <osgViewer/ViewerBase>
#include <QInputEvent>
#include <QPointer>
#include <QLCDNumber>

#if (QT_VERSION>=QT_VERSION_CHECK(4, 6, 0))
# define USE_GESTURES
# include <QGestureEvent>
# include <QGesture>
#endif

class QtKeyboardMap
{
public:
    QtKeyboardMap()
    {
        mKeyMap[Qt::Key_Escape     ] = osgGA::GUIEventAdapter::KEY_Escape;
        mKeyMap[Qt::Key_Delete   ] = osgGA::GUIEventAdapter::KEY_Delete;
        mKeyMap[Qt::Key_Home       ] = osgGA::GUIEventAdapter::KEY_Home;
        mKeyMap[Qt::Key_Enter      ] = osgGA::GUIEventAdapter::KEY_KP_Enter;
        mKeyMap[Qt::Key_End        ] = osgGA::GUIEventAdapter::KEY_End;
        mKeyMap[Qt::Key_Return     ] = osgGA::GUIEventAdapter::KEY_Return;
        mKeyMap[Qt::Key_PageUp     ] = osgGA::GUIEventAdapter::KEY_Page_Up;
        mKeyMap[Qt::Key_PageDown   ] = osgGA::GUIEventAdapter::KEY_Page_Down;
        mKeyMap[Qt::Key_Left       ] = osgGA::GUIEventAdapter::KEY_Left;
        mKeyMap[Qt::Key_Right      ] = osgGA::GUIEventAdapter::KEY_Right;
        mKeyMap[Qt::Key_Up         ] = osgGA::GUIEventAdapter::KEY_Up;
        mKeyMap[Qt::Key_Down       ] = osgGA::GUIEventAdapter::KEY_Down;
        mKeyMap[Qt::Key_Backspace  ] = osgGA::GUIEventAdapter::KEY_BackSpace;
        mKeyMap[Qt::Key_Tab        ] = osgGA::GUIEventAdapter::KEY_Tab;
        mKeyMap[Qt::Key_Space      ] = osgGA::GUIEventAdapter::KEY_Space;
        mKeyMap[Qt::Key_Delete     ] = osgGA::GUIEventAdapter::KEY_Delete;
        mKeyMap[Qt::Key_Alt      ] = osgGA::GUIEventAdapter::KEY_Alt_L;
        mKeyMap[Qt::Key_Shift    ] = osgGA::GUIEventAdapter::KEY_Shift_L;
        mKeyMap[Qt::Key_Control  ] = osgGA::GUIEventAdapter::KEY_Control_L;
        mKeyMap[Qt::Key_Meta     ] = osgGA::GUIEventAdapter::KEY_Meta_L;

        mKeyMap[Qt::Key_F1             ] = osgGA::GUIEventAdapter::KEY_F1;
        mKeyMap[Qt::Key_F2             ] = osgGA::GUIEventAdapter::KEY_F2;
        mKeyMap[Qt::Key_F3             ] = osgGA::GUIEventAdapter::KEY_F3;
        mKeyMap[Qt::Key_F4             ] = osgGA::GUIEventAdapter::KEY_F4;
        mKeyMap[Qt::Key_F5             ] = osgGA::GUIEventAdapter::KEY_F5;
        mKeyMap[Qt::Key_F6             ] = osgGA::GUIEventAdapter::KEY_F6;
        mKeyMap[Qt::Key_F7             ] = osgGA::GUIEventAdapter::KEY_F7;
        mKeyMap[Qt::Key_F8             ] = osgGA::GUIEventAdapter::KEY_F8;
        mKeyMap[Qt::Key_F9             ] = osgGA::GUIEventAdapter::KEY_F9;
        mKeyMap[Qt::Key_F10            ] = osgGA::GUIEventAdapter::KEY_F10;
        mKeyMap[Qt::Key_F11            ] = osgGA::GUIEventAdapter::KEY_F11;
        mKeyMap[Qt::Key_F12            ] = osgGA::GUIEventAdapter::KEY_F12;
        mKeyMap[Qt::Key_F13            ] = osgGA::GUIEventAdapter::KEY_F13;
        mKeyMap[Qt::Key_F14            ] = osgGA::GUIEventAdapter::KEY_F14;
        mKeyMap[Qt::Key_F15            ] = osgGA::GUIEventAdapter::KEY_F15;
        mKeyMap[Qt::Key_F16            ] = osgGA::GUIEventAdapter::KEY_F16;
        mKeyMap[Qt::Key_F17            ] = osgGA::GUIEventAdapter::KEY_F17;
        mKeyMap[Qt::Key_F18            ] = osgGA::GUIEventAdapter::KEY_F18;
        mKeyMap[Qt::Key_F19            ] = osgGA::GUIEventAdapter::KEY_F19;
        mKeyMap[Qt::Key_F20            ] = osgGA::GUIEventAdapter::KEY_F20;

        mKeyMap[Qt::Key_hyphen         ] = '-';
        mKeyMap[Qt::Key_Equal         ] = '=';

        mKeyMap[Qt::Key_division      ] = osgGA::GUIEventAdapter::KEY_KP_Divide;
        mKeyMap[Qt::Key_multiply      ] = osgGA::GUIEventAdapter::KEY_KP_Multiply;
        mKeyMap[Qt::Key_Minus         ] = '-';
        mKeyMap[Qt::Key_Plus          ] = '+';
        //mKeyMap[Qt::Key_H              ] = osgGA::GUIEventAdapter::KEY_KP_Home;
        //mKeyMap[Qt::Key_                    ] = osgGA::GUIEventAdapter::KEY_KP_Up;
        //mKeyMap[92                    ] = osgGA::GUIEventAdapter::KEY_KP_Page_Up;
        //mKeyMap[86                    ] = osgGA::GUIEventAdapter::KEY_KP_Left;
        //mKeyMap[87                    ] = osgGA::GUIEventAdapter::KEY_KP_Begin;
        //mKeyMap[88                    ] = osgGA::GUIEventAdapter::KEY_KP_Right;
        //mKeyMap[83                    ] = osgGA::GUIEventAdapter::KEY_KP_End;
        //mKeyMap[84                    ] = osgGA::GUIEventAdapter::KEY_KP_Down;
        //mKeyMap[85                    ] = osgGA::GUIEventAdapter::KEY_KP_Page_Down;
        mKeyMap[Qt::Key_Insert        ] = osgGA::GUIEventAdapter::KEY_KP_Insert;
        //mKeyMap[Qt::Key_Delete        ] = osgGA::GUIEventAdapter::KEY_KP_Delete;
    }

    ~QtKeyboardMap()
    {
    }

    int remapKey(QKeyEvent* event)
    {
        KeyMap::iterator itr = mKeyMap.find(event->key());
        if (itr == mKeyMap.end())
        {
            return int(*(event->text().toLatin1().data()));
        }
        else
            return itr->second;
    }

    private:
    typedef std::map<unsigned int, int> KeyMap;
    KeyMap mKeyMap;
};

static QtKeyboardMap s_QtKeyboardMap;

#if (QT_VERSION < QT_VERSION_CHECK(5, 2, 0))
    #define GETDEVICEPIXELRATIO() 1.0
#else
    #define GETDEVICEPIXELRATIO() devicePixelRatio()
#endif

void OSGWidget::updateFPS()
{
    static double accTime = 0;
    static const double updateFrameTime = 0.5;
    double paintTime = osg::Timer::instance()->time_s();
    double deltaTime = paintTime - mLastTime;
    if (accTime > updateFrameTime)
    {
        mFPSWidget->display(1.0 / deltaTime);
        accTime = 0;
    }
    else
    {
        accTime += deltaTime;
    }
    mLastTime = osg::Timer::instance()->time_s();
}

OSGWidget::OSGWidget(QWidget*parent, Qt::WindowFlags f, bool forwardKeyEvents)
    : QOpenGLWidget(parent, f),
      _gw( NULL ),
      _touchEventsEnabled( false ),
      _forwardKeyEvents( forwardKeyEvents )
{
    _devicePixelRatio = GETDEVICEPIXELRATIO();

    mLastTime = 0;
    mFPSWidget = new QLCDNumber(this);
    mFPSWidget->setObjectName(QStringLiteral("lcdNumber"));
    mFPSWidget->setGeometry(QRect(10, 10, 40, 20));
    mFPSWidget->setFrameShape(QFrame::NoFrame);
    mFPSWidget->setFrameShadow(QFrame::Raised);
    mFPSWidget->setSmallDecimalPoint(false);
    mFPSWidget->setDigitCount(5);
    mFPSWidget->setSegmentStyle(QLCDNumber::Outline);
}

OSGWidget::~OSGWidget()
{
    // close GraphicsWindowQt and remove the reference to us
    if( _gw )
    {
        //_gw->close();
        _gw = NULL;
    }
}

void OSGWidget::initializeGL()
{
    initializeOpenGLFunctions();

    glClearColor(0, 0, 0, 1);
    glDisable(GL_LIGHTING);
    glDisable(GL_ALPHA_TEST);
    glDisable(GL_DEPTH_TEST);
    glDisable(GL_STENCIL_TEST);
    glClear(NULL);
}
void OSGWidget::resizeGL(int w, int h)
{
    // do nothing
}
void OSGWidget::paintGL()
{
    if (_gw == NULL || !_gw->isRealized() || _gw->isCurrent())
    {
        return;
    }

    osg::GraphicsContext::Cameras& cams = _gw->getCameras();

    if (cams.size() == 0)
        return;

    osg::Camera* cam = *(cams.begin());
    if (cam)
    {
        const osg::Camera::BufferAttachmentMap& attachMap = cam->getBufferAttachmentMap();
        osg::Camera::BufferAttachmentMap::const_iterator citr = attachMap.find(osg::Camera::COLOR_BUFFER0);
        if (citr != attachMap.cend())
        {
            const osg::Camera::Attachment& attach = citr->second;
            if (attach._image->valid())
            {
                osg::Image* img = attach._image;
                glDrawPixels(
                    img->s(),
                    img->t(),
                    img->getInternalTextureFormat(),
                    img->getDataType(),
                    img->data());
                updateFPS();
            }
        }
    }
}

void OSGWidget::setTouchEventsEnabled(bool e)
{
#ifdef USE_GESTURES
    if (e==_touchEventsEnabled)
        return;

    _touchEventsEnabled = e;

    if (_touchEventsEnabled)
    {
        grabGesture(Qt::PinchGesture);
    }
    else
    {
        ungrabGesture(Qt::PinchGesture);
    }
#endif
}

void OSGWidget::processDeferredEvents()
{
    QQueue<QEvent::Type> deferredEventQueueCopy;
    {
        QMutexLocker lock(&_deferredEventQueueMutex);
        deferredEventQueueCopy = _deferredEventQueue;
        _eventCompressor.clear();
        _deferredEventQueue.clear();
    }

    while (!deferredEventQueueCopy.isEmpty())
    {
        QEvent event(deferredEventQueueCopy.dequeue());
        inherited::event(&event);
    }
}

bool OSGWidget::event(QEvent* event)
{
#ifdef USE_GESTURES
    if ( event->type()==QEvent::Gesture )
        return gestureEvent(static_cast<QGestureEvent*>(event));
#endif

    // QEvent::Hide
    //
    // workaround "Qt-workaround" that does glFinish before hiding the widget
    // (the Qt workaround was seen at least in Qt 4.6.3 and 4.7.0)
    //
    // Qt makes the context current, performs glFinish, and releases the context.
    // This makes the problem in OSG multithreaded environment as the context
    // is active in another thread, thus it can not be made current for the purpose
    // of glFinish in this thread.

    // QEvent::ParentChange
    //
    // Reparenting GLWidget may create a new underlying window and a new GL context.
    // Qt will then call doneCurrent on the GL context about to be deleted. The thread
    // where old GL context was current has no longer current context to render to and
    // we cannot make new GL context current in this thread.

    // We workaround above problems by deferring execution of problematic event requests.
    // These events has to be enqueue and executed later in a main GUI thread (GUI operations
    // outside the main thread are not allowed) just before makeCurrent is called from the
    // right thread. The good place for doing that is right after swap in a swapBuffersImplementation.

    if (event->type() == QEvent::Hide)
    {
        // enqueue only the last of QEvent::Hide and QEvent::Show
        enqueueDeferredEvent(QEvent::Hide, QEvent::Show);
        return true;
    }
    else if (event->type() == QEvent::Show)
    {
        // enqueue only the last of QEvent::Show or QEvent::Hide
        enqueueDeferredEvent(QEvent::Show, QEvent::Hide);
        return true;
    }
    else if (event->type() == QEvent::ParentChange)
    {
        // enqueue only the last QEvent::ParentChange
        enqueueDeferredEvent(QEvent::ParentChange);
        return true;
    }

    // perform regular event handling
    return inherited::event( event );
}

void OSGWidget::setKeyboardModifiers(QInputEvent* event)
{
    int modkey = event->modifiers() & (Qt::ShiftModifier | Qt::ControlModifier | Qt::AltModifier);
    unsigned int mask = 0;
    if ( modkey & Qt::ShiftModifier ) mask |= osgGA::GUIEventAdapter::MODKEY_SHIFT;
    if ( modkey & Qt::ControlModifier ) mask |= osgGA::GUIEventAdapter::MODKEY_CTRL;
    if ( modkey & Qt::AltModifier ) mask |= osgGA::GUIEventAdapter::MODKEY_ALT;
    _gw->getEventQueue()->getCurrentEventState()->setModKeyMask( mask );
}

void OSGWidget::resizeEvent(QResizeEvent* event)
{
    const QSize& size = event->size();

    int scaled_width = static_cast<int>(size.width()*_devicePixelRatio);
    int scaled_height = static_cast<int>(size.height()*_devicePixelRatio);
    _gw->resized( x(), y(), scaled_width,  scaled_height);
    _gw->getEventQueue()->windowResize( x(), y(), scaled_width, scaled_height );
    _gw->requestRedraw();

    return inherited::resizeEvent(event);
}

void OSGWidget::moveEvent(QMoveEvent* event)
{
    const QPoint& pos = event->pos();
    int scaled_width = static_cast<int>(width()*_devicePixelRatio);
    int scaled_height = static_cast<int>(height()*_devicePixelRatio);
    _gw->resized( pos.x(), pos.y(), scaled_width,  scaled_height );
    _gw->getEventQueue()->windowResize( pos.x(), pos.y(), scaled_width,  scaled_height );

    return inherited::moveEvent(event);
}

void OSGWidget::keyPressEvent(QKeyEvent* event)
{
    setKeyboardModifiers( event );
    int value = s_QtKeyboardMap.remapKey( event );
    _gw->getEventQueue()->keyPress( value );

    // this passes the event to the regular Qt key event processing,
    // among others, it closes popup windows on ESC and forwards the event to the parent widgets
    if( _forwardKeyEvents )
        inherited::keyPressEvent( event );
}

void OSGWidget::keyReleaseEvent(QKeyEvent* event)
{
    if( event->isAutoRepeat() )
    {
        event->ignore();
    }
    else
    {
        setKeyboardModifiers( event );
        int value = s_QtKeyboardMap.remapKey( event );
        _gw->getEventQueue()->keyRelease( value );
    }

    // this passes the event to the regular Qt key event processing,
    // among others, it closes popup windows on ESC and forwards the event to the parent widgets
    if( _forwardKeyEvents )
        inherited::keyReleaseEvent( event );
}

void OSGWidget::mousePressEvent(QMouseEvent* event)
{
    int button = 0;
    switch ( event->button() )
    {
        case Qt::LeftButton: button = 1; break;
        case Qt::MidButton: button = 2; break;
        case Qt::RightButton: button = 3; break;
        case Qt::NoButton: button = 0; break;
        default: button = 0; break;
    }
    setKeyboardModifiers( event );
    _gw->getEventQueue()->mouseButtonPress( event->x()*_devicePixelRatio, event->y()*_devicePixelRatio, button );
}

void OSGWidget::mouseReleaseEvent(QMouseEvent* event)
{
    int button = 0;
    switch ( event->button() )
    {
        case Qt::LeftButton: button = 1; break;
        case Qt::MidButton: button = 2; break;
        case Qt::RightButton: button = 3; break;
        case Qt::NoButton: button = 0; break;
        default: button = 0; break;
    }
    setKeyboardModifiers( event );
    _gw->getEventQueue()->mouseButtonRelease( event->x()*_devicePixelRatio, event->y()*_devicePixelRatio, button );
}

void OSGWidget::mouseDoubleClickEvent(QMouseEvent* event)
{
    int button = 0;
    switch ( event->button() )
    {
        case Qt::LeftButton: button = 1; break;
        case Qt::MidButton: button = 2; break;
        case Qt::RightButton: button = 3; break;
        case Qt::NoButton: button = 0; break;
        default: button = 0; break;
    }
    setKeyboardModifiers( event );
    _gw->getEventQueue()->mouseDoubleButtonPress( event->x()*_devicePixelRatio, event->y()*_devicePixelRatio, button );
}

void OSGWidget::mouseMoveEvent(QMouseEvent* event)
{
    setKeyboardModifiers( event );
    _gw->getEventQueue()->mouseMotion( event->x()*_devicePixelRatio, event->y()*_devicePixelRatio );
}

void OSGWidget::wheelEvent(QWheelEvent* event)
{
    setKeyboardModifiers( event );
    _gw->getEventQueue()->mouseScroll(
        event->orientation() == Qt::Vertical ?
            (event->delta()>0 ? osgGA::GUIEventAdapter::SCROLL_UP : osgGA::GUIEventAdapter::SCROLL_DOWN) :
            (event->delta()>0 ? osgGA::GUIEventAdapter::SCROLL_LEFT : osgGA::GUIEventAdapter::SCROLL_RIGHT) );
}

#ifdef USE_GESTURES
static osgGA::GUIEventAdapter::TouchPhase translateQtGestureState( Qt::GestureState state )
{
    osgGA::GUIEventAdapter::TouchPhase touchPhase;
    switch ( state )
    {
        case Qt::GestureStarted:
            touchPhase = osgGA::GUIEventAdapter::TOUCH_BEGAN;
            break;
        case Qt::GestureUpdated:
            touchPhase = osgGA::GUIEventAdapter::TOUCH_MOVED;
            break;
        case Qt::GestureFinished:
        case Qt::GestureCanceled:
            touchPhase = osgGA::GUIEventAdapter::TOUCH_ENDED;
            break;
        default:
            touchPhase = osgGA::GUIEventAdapter::TOUCH_UNKNOWN;
    };

    return touchPhase;
}
#endif


bool OSGWidget::gestureEvent(QGestureEvent* qevent)
{
#ifndef USE_GESTURES
    return false;
#else

    bool accept = false;

    if ( QPinchGesture* pinch = static_cast<QPinchGesture *>(qevent->gesture(Qt::PinchGesture) ) )
    {
    const QPointF qcenterf = pinch->centerPoint();
    const float angle = pinch->totalRotationAngle();
    const float scale = pinch->totalScaleFactor();

    const QPoint pinchCenterQt = mapFromGlobal(qcenterf.toPoint());
    const osg::Vec2 pinchCenter( pinchCenterQt.x(), pinchCenterQt.y() );

        //We don't have absolute positions of the two touches, only a scale and rotation
        //Hence we create pseudo-coordinates which are reasonable, and centered around the
        //real position
        const float radius = (width()+height())/4;
        const osg::Vec2 vector( scale*cos(angle)*radius, scale*sin(angle)*radius);
        const osg::Vec2 p0 = pinchCenter+vector;
        const osg::Vec2 p1 = pinchCenter-vector;

        osg::ref_ptr<osgGA::GUIEventAdapter> event = 0;
        const osgGA::GUIEventAdapter::TouchPhase touchPhase = translateQtGestureState( pinch->state() );
        if ( touchPhase==osgGA::GUIEventAdapter::TOUCH_BEGAN )
        {
            event = _gw->getEventQueue()->touchBegan(0 , touchPhase, p0[0], p0[1] );
        }
        else if ( touchPhase==osgGA::GUIEventAdapter::TOUCH_MOVED )
        {
            event = _gw->getEventQueue()->touchMoved( 0, touchPhase, p0[0], p0[1] );
        }
        else
        {
            event = _gw->getEventQueue()->touchEnded( 0, touchPhase, p0[0], p0[1], 1 );
        }

        if ( event )
        {
            event->addTouchPoint( 1, touchPhase, p1[0], p1[1] );
            accept = true;
        }
    }

    if ( accept )
        qevent->accept();

    return accept;
#endif
}


/// The object responsible for the scene re-rendering.
class HeartBeat : public QObject {
public:
    int _timerId;
    QWidget* _widget;
    osg::Timer _lastFrameStartTime;
    osg::observer_ptr< osgViewer::ViewerBase > _viewer;
    virtual ~HeartBeat();

    void init(osgViewer::ViewerBase *viewer, QWidget* widget, int interval);
    void stopTimer();
    void timerEvent(QTimerEvent *event);

    static HeartBeat* instance();
private:
    HeartBeat();

    static QPointer<HeartBeat> heartBeat;
};

QPointer<HeartBeat> HeartBeat::heartBeat;

void setViewer(osgViewer::ViewerBase *viewer, QWidget* widget, int interval)
{
    HeartBeat::instance()->init( viewer, widget, interval);
}


/// Constructor. Must be called from main thread.
HeartBeat::HeartBeat() : _timerId(0), _widget(NULL)
{
}


/// Destructor. Must be called from main thread.
HeartBeat::~HeartBeat()
{
    stopTimer();
}

HeartBeat* HeartBeat::instance()
{
    if (!heartBeat)
    {
        heartBeat = new HeartBeat();
    }
    return heartBeat;
}

void HeartBeat::stopTimer()
{
    if ( _timerId != 0 )
    {
        killTimer( _timerId );
        _timerId = 0;
    }
}


/// Initializes the loop for viewer. Must be called from main thread.
void HeartBeat::init(osgViewer::ViewerBase *viewer, QWidget* widget, int interval)
{
    if( _viewer == viewer )
        return;

    stopTimer();

    _viewer = viewer;

    if( viewer )
    {
        _widget = widget;
        _timerId = startTimer( interval );
        _lastFrameStartTime.setStartTick( 0 );
    }
}


void HeartBeat::timerEvent( QTimerEvent *  )
{
    osg::ref_ptr< osgViewer::ViewerBase > viewer;
    if( !_viewer.lock( viewer ) )
    {
        // viewer has been deleted -> stop timer
        stopTimer();
        return;
    }

    // limit the frame rate
    if( viewer->getRunMaxFrameRate() > 0.0)
    {
        double dt = _lastFrameStartTime.time_s();
        double minFrameTime = 1.0 / viewer->getRunMaxFrameRate();
        if (dt < minFrameTime)
            OpenThreads::Thread::microSleep(static_cast<unsigned int>(1000000.0*(minFrameTime-dt)));
    }
    else
    {
        // avoid excessive CPU loading when no frame is required in ON_DEMAND mode
        if( viewer->getRunFrameScheme() == osgViewer::ViewerBase::ON_DEMAND )
        {
            double dt = _lastFrameStartTime.time_s();
            if (dt < 0.01)
                OpenThreads::Thread::microSleep(static_cast<unsigned int>(1000000.0*(0.01-dt)));
        }

        // record start frame time
        _lastFrameStartTime.setStartTick();

        // make frame
        bool framed = false;
        if( viewer->getRunFrameScheme() == osgViewer::ViewerBase::ON_DEMAND )
        {
            if( viewer->checkNeedToDoFrame() )
            {
                viewer->frame();
                framed = true;
            }
        }
        else
        {
            viewer->frame();
            framed = true;
        }

        if (framed && _widget)
        {
            _widget->repaint();
        }
    }
}
